/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.valarnin.plugins.citygen.generator;

import com.valarnin.plugins.citygen.populators.CityBlockPopulator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.Chunk;

/**
 *
 * @author valarnin
 */
public class CityGen {
    private static CityGen generator = null;
    
    private LinkedList<City> cities;
    
    private HashMap<String, LinkedList<CityBlockPopulator>> populators = null;
    
    private CityGen() {
    }
    /**
     * Gets the singleton instance of the city generator
     * @return 
     */
    public static CityGen GetCityGenerator() {
        if(generator == null) {
            generator = new CityGen();
        }
        
        return generator;
    }
    
    /**
     * Determines if the chunk specified by x, z is adjacent to any cities
     */
    public boolean isAdjacentToCity(int x, int z) {
        for(City city : cities) {
            if(city.isAdjacent(x, z)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Determines if the chunk specified by x, z is required by any city
     */
    public boolean isRequiredCityBlock(int x, int z) {
        for(City city : cities) {
            if(city.isRequired(x, z)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Adds the specified chunk to the first applicable city
     * @param chunk The chunk to add
     * @param random The global Bukkit RNG
     * @return True if the chunk was added to a city, otherwise false
     */
    public boolean addChunk(Chunk chunk, Random random) {
        for(City city : cities) {
            if(city.isAdjacent(chunk.getX(), chunk.getZ())) {
                return city.addChunk(chunk, random);
            }
        }
        return false;
    }
    
    /**
     * Adds a new city starting with the specified chunk
     * @param chunk The chunk to create the city with
     * @param random The global Bukkit RNG
     * @return True if it could successfully add the city, otherwise false
     */
    public boolean addCity(Chunk chunk, Random random) {
        if(isAdjacentToCity(chunk.getX(), chunk.getZ())) {
            return false;
        }
        
        City city = new City();
        city.addChunk(chunk, random);
        return cities.add(city);
    }

    /**
     * Gets the amount of chunks in whichever city is adjacent to the specified
     * chunk coordinates
     * @param x
     * @param z
     * @return 
     */
    public int getTotalChunksAdjacent(int x, int z) {
        for(City city : cities) {
            if(city.isAdjacent(x, z)) {
                return city.getTotalChunks();
            }
        }
        return 0;
    }

    /**
     * Gets the distance to the closest city specified by the chunk coordinates
     * @param x
     * @param z
     * @return 
     */
    public double getClosestCityDistance(int x, int z) {
        double distance = Double.MAX_VALUE;
        for(City city : cities) {
            distance = Math.min(distance, city.getDistance(x, z));
        }
        return distance;
    }

    /**
     * Populates a specified CityBlock with a random CityBlockPopulator
     * @param block
     * @param random 
     */
    public void populate(CityBlock block, Random random) {
        if(populators == null) {
            initializePopulators();
        }
        
        String blockPopulatorsKey = block.getWidth() + "x" + block.getDepth();
        
        LinkedList<CityBlockPopulator> blockPopulators = populators.get(blockPopulatorsKey);
        
        blockPopulators.get(random.nextInt(blockPopulators.size())).populate(block, random);
    }

    /**
     * Initializes all populators which fall under the correct package (com.valarnin.plugins.citygen.populators)
     */
    private void initializePopulators() {
        populators = new HashMap<String, LinkedList<CityBlockPopulator>>();
        
        String packageName = getClass().getPackage().getName() + ".populators";
        
        Class[] classes = ClassPackageUtil.getClasses(packageName);
        
        for(int classNum = 0; classNum < classes.length; ++classNum) {
            if(!ArrayUtils.contains(classes[classNum].getInterfaces(), CityBlockPopulator.class)) {
                classes = (Class[]) ArrayUtils.remove(classes, classNum);
            }
        }
        
        //Remove the main interface
        classes = (Class[]) ArrayUtils.remove(classes, ArrayUtils.indexOf(classes, CityBlockPopulator.class));
        
        for(Class clazz : classes) {
            try {
                CityBlockPopulator populator = (CityBlockPopulator) clazz.newInstance();
                populator.register(this);
            } catch (InstantiationException ex) {
                Logger.getLogger(CityGen.class.getName()).log(Level.SEVERE, "Error instantiating populator `" + clazz.getName() + "` for CityGen", ex);
            } catch (IllegalAccessException ex) {
                Logger.getLogger(CityGen.class.getName()).log(Level.SEVERE, "Error instantiating populator `" + clazz.getName() + "` for CityGen", ex);
            }
        }
    }
    
    /**
     * Registers a CityBlockPopulator for use with the system.
     * Note that all configuration loading for a populator should happen within
     * the constructor.
     * @param width The width that the CityBlockPopulator supports
     * @param depth The depth that the CityBlockPopulator supports
     * @param populator An instance of the CityBlockPopulator to register
     */
    public void registerPopulator(int width, int depth, CityBlockPopulator populator) {
        String blockPopulatorsKey = width + "x" + depth;
        LinkedList<CityBlockPopulator> blockPopulators = populators.get(blockPopulatorsKey);
        
        if(blockPopulators == null) {
            blockPopulators = new LinkedList<CityBlockPopulator>();
        }
        
        blockPopulators.add(populator);
        
        populators.put(blockPopulatorsKey, blockPopulators);
    }
}
